package com.vaadin.thomas.contenttool.data.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;

import com.vaadin.thomas.contenttool.data.Localization;
import com.vaadin.thomas.contenttool.data.Tag;
import com.vaadin.thomas.contenttool.data.Value;
import com.vaadin.thomas.contenttool.data.Version;

class ValueImpl extends AbstractData implements Value {

	public ValueImpl(Node n, ModelImpl m) {
		super(n, m);
	}

	static final String PROP_ID = "identifier";
	static final String PROP_DELETED = "deleted";

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#getTags()
	 */
	@Override
	public Set<Tag> getTags() {
		Set<Tag> tags = new HashSet<Tag>();
		for (Relationship r : node.getRelationships(References.TAG_TO_VALUES)) {
			tags.add(new TagImpl(r.getStartNode(), model));
		}
		return tags;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.vaadin.thomas.contenttool.data.IValue#removeTag(com.vaadin.thomas
	 * .contenttool.data.Tag)
	 */
	@Override
	public void removeTag(Tag t) {

		checkPreview();

		model.logDebug("Removing tag '" + t.getValue() + "' from value '"
				+ getKey() + "'");

		for (Relationship r : node.getRelationships(References.TAG_TO_VALUES)) {
			Tag tag = new TagImpl(r.getStartNode(), model);

			if (t.equals(tag)) {
				Transaction tx = node.getGraphDatabase().beginTx();
				try {
					Node tagNode = r.getOtherNode(node);
					r.delete();

					// delete tag if there are no references to it
					if (!tagNode.getRelationships(References.TAG_TO_VALUES)
							.iterator().hasNext()) {
						model.logDebug("No references to Tag '" + t.getValue()
								+ "' left, removing it completely");
						tagNode.getSingleRelationship(References.TAG_TO_TAGS,
								Direction.INCOMING).delete();
						tagNode.delete();
					}

					tx.success();
				} catch (RuntimeException e) {
					tx.failure();
					throw e;
				} finally {
					tx.finish();
				}
				break;
			}
		}
	}

	private void checkPreview() {
		if (!getVersion().isPreview()) {
			throw new RuntimeException("only allowed to modify preview version");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#addTag(com.vaadin.thomas.
	 * contenttool.data.Tag)
	 */
	@Override
	public void addTag(Tag t) {

		checkPreview();

		model.logDebug("Adding tag '" + t.getValue() + "' to value '" + getKey()
				+ "'");

		if (getTags().contains(t)) {
			return;
		}

		Transaction tx = node.getGraphDatabase().beginTx();
		try {
			((TagImpl) t).node.createRelationshipTo(node,
					References.TAG_TO_VALUES);
			tx.success();
		} catch (RuntimeException e) {
			tx.failure();
			throw e;
		} finally {
			tx.finish();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#getId()
	 */
	@Override
	public String getKey() {
		return (String) node.getProperty(PROP_ID);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#getLocalizations()
	 */
	@Override
	public Map<Locale, List<Localization>> getLocalizations() {

		Map<Locale, List<Localization>> m = new HashMap<Locale, List<Localization>>();
		for (Relationship r : node.getRelationships(References.VALUE_TO_LOC)) {
			Localization l = new LocalizationImpl(r.getEndNode(), model);

			if (m.get(l.getLanguage()) == null) {
				m.put(l.getLanguage(), new ArrayList<Localization>());
			}
			m.get(l.getLanguage()).add(l);
		}
		return m;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#getAllLocalizations()
	 */
	@Override
	public Map<Locale, List<Localization>> getAllLocalizations() {

		Map<Locale, List<Localization>> m = new HashMap<Locale, List<Localization>>();

		if (getPreviousVersion() != null) {
			m.putAll(getPreviousVersion().getAllLocalizations());
		}
		m.putAll(getLocalizations());
		return m;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.vaadin.thomas.contenttool.data.IValue#addLocalization(com.vaadin.
	 * thomas.contenttool.data.Localization)
	 */
	@Override
	public void addLocalization(Localization loc) {

		checkPreview();

		model.logDebug("Adding localization '" + loc.getLanguage()
				+ "' for value '" + getKey() + "'");

		Transaction tx = node.getGraphDatabase().beginTx();
		try {
			node.createRelationshipTo(((LocalizationImpl) loc).node,
					References.VALUE_TO_LOC);
			tx.success();
		} catch (RuntimeException e) {
			tx.failure();
			throw e;
		} finally {
			tx.finish();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#getPreviousVersion()
	 */
	@Override
	public Value getPreviousVersion() {
		return getVersion(Direction.INCOMING);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#getNextVersion()
	 */
	@Override
	public Value getNextVersion() {
		return getVersion(Direction.OUTGOING);
	}

	private Value getVersion(Direction d) {
		Relationship r = node.getSingleRelationship(References.VALUE_TO_NEWER,
				d);
		if (r == null) {
			return null;
		}

		Value v = new ValueImpl(r.getOtherNode(node), model);
		return v;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#getVersion()
	 */
	@Override
	public Version getVersion() {

		return new VersionImpl(node.getSingleRelationship(
				References.VERSION_TO_VALUES, Direction.INCOMING)
				.getStartNode(), model);
	}

	public static ValueImpl create(ModelImpl m, String identifier,
			LocalizationImpl... locs) {

		m.logInfo("Creating value '" + identifier + "'");

		Transaction tx = m.db.beginTx();

		ValueImpl v = null;
		try {
			Node n = m.db.createNode();
			n.setProperty(PROP_ID, identifier);
			n.setProperty(PROP_DELETED, false);

			for (LocalizationImpl l : locs) {
				n.createRelationshipTo(l.node, References.VALUE_TO_LOC);
			}

			v = new ValueImpl(n, m);

			tx.success();
		} catch (RuntimeException e) {
			tx.failure();
			throw e;
		} finally {
			tx.finish();
		}

		return v;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#markForDelete()
	 */
	@Override
	public void markForDelete() {

		model.logDebug("Marking value '" + getKey() + "' for deletion");

		checkPreview();

		Transaction tx = node.getGraphDatabase().beginTx();
		try {
			node.setProperty(PROP_DELETED, true);
			tx.success();
		} catch (RuntimeException e) {
			tx.failure();
			throw e;
		} finally {
			tx.finish();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#isDeleted()
	 */
	@Override
	public boolean isDeleted() {
		return (Boolean) node.getProperty(PROP_DELETED);
	}

	@Override
	public int compareTo(Value other) {
		return getKey().compareTo(other.getKey());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.vaadin.thomas.contenttool.data.IValue#unDelete()
	 */
	@Override
	public void unDelete() {

		checkPreview();

		model.logDebug("Removing delete mark for value '" + getKey() + "'");

		Transaction tx = node.getGraphDatabase().beginTx();
		try {
			node.setProperty(PROP_DELETED, false);
			tx.success();
		} catch (RuntimeException e) {
			tx.failure();
			throw e;
		} finally {
			tx.finish();
		}
	}

}
